package repository

import (
	"context"
	"fmt"
	"invitation/internal/repository/cache"
	"sync"

	"github.com/rs/zerolog/log"
	"gorm.io/gorm"
)

// RepoRegistry 是一个通用的、线程安全的对象注册表。
// 它用于根据一个工厂函数来创建和缓存单例对象。
// T 是我们想要存储和获取的对象类型。
type RepoRegistry[T any] struct {
	mu     sync.RWMutex
	stores map[string]T
}

// NewRepoRegistry 创建一个新的 RepoRegistry 实例。
func NewRepoRegistry[T any]() *RepoRegistry[T] {
	return &RepoRegistry[T]{
		stores: make(map[string]T),
	}
}

// GetOrAdd 是核心方法。
// 它尝试从注册表中获取一个名为 name 的对象。
// 如果对象不存在，它会调用 factory 函数来创建一个，存入注册表，然后返回。
// 这是线程安全的。
func (r *RepoRegistry[T]) GetOrAdd(name string, factory func() T) T {
	r.mu.RLock()
	// 尝试从 map 中读取
	if repo, exists := r.stores[name]; exists {
		r.mu.RUnlock()
		return repo
	}
	r.mu.RUnlock()

	// 如果不存在，则进入创建流程
	r.mu.Lock()
	defer r.mu.Unlock()

	// 双重检查，防止在等待锁时，其他 goroutine 已经创建了该对象
	if repo, exists := r.stores[name]; exists {
		return repo
	}

	// 调用工厂函数创建新实例
	newRepo := factory()
	r.stores[name] = newRepo
	return newRepo
}
func (r *RepoRegistry[T]) Set(name string, value T) {
	r.mu.Lock()
	defer r.mu.Unlock()
	r.stores[name] = value
}

func (r *RepoRegistry[T]) Get(name string) T {
	r.mu.RLock()
	defer r.mu.RUnlock()
	return r.stores[name]
}

type contextKey string

const txKey contextKey = "transaction"
const (
	GameInfoRepoName             = "game_info"
	GameIntroductionRepoName     = "game_introduction"
	GameTheoryRepoName           = "game_theory"
	FirstTimeTeacherInfoRepoName = "first_time_teacher_info"
	FirstTimePostportRepoName    = "first_time_postport"
)

type repoConfig struct {
	name    string
	factory func(*gorm.DB, *cache.LRU, string) Repository
}

var repoConfigs = []repoConfig{
	{GameInfoRepoName, NewGameInfoRepositoryWithCache},
	{GameIntroductionRepoName, NewGameIntroudctionRepositoryWithCache},
	{GameTheoryRepoName, NewGameTheoryRepositoryWithCache},
	{FirstTimeTeacherInfoRepoName, NewFirstTimeTeacherInfoRepositoryWithCache},
	{FirstTimePostportRepoName, NewFirstTimePostportRepositoryWithCache},
}

type RepoManager struct {
	db                    *gorm.DB
	cache                 *cache.LRU
	repoRegistry          *RepoRegistry[func(*gorm.DB) Repository]
	txRepoFactoryRegistry *RepoRegistry[func(tx *gorm.DB) Repository]
}

func NewRepoManager(db *gorm.DB, cache *cache.LRU) *RepoManager {
	registry := NewRepoRegistry[func(*gorm.DB) Repository]()
	txFactoryRegistry := NewRepoRegistry[func(tx *gorm.DB) Repository]()
	for _, cfg := range repoConfigs {
		finalCfg := cfg // 创建副本
		registry.Set(cfg.name, func(db *gorm.DB) Repository {
			return finalCfg.factory(db, cache, finalCfg.name)
		})
		txFactoryRegistry.Set(cfg.name, func(tx *gorm.DB) Repository {
			return finalCfg.factory(tx, cache, finalCfg.name)
		})
	}
	return &RepoManager{
		db:                    db,
		cache:                 cache,
		repoRegistry:          registry,
		txRepoFactoryRegistry: txFactoryRegistry,
	}
}

func (rm *RepoManager) DB() *gorm.DB {
	return rm.db
}

func (rm *RepoManager) Cache() *cache.LRU {
	return rm.cache
}

func (rm *RepoManager) getDBFromContext(ctx context.Context) *gorm.DB {
	if tx, ok := ctx.Value(txKey).(*gorm.DB); ok {
		return tx
	}
	return rm.DB()
}
func getRepository[T any](
	ctx context.Context,
	rm *RepoManager,
	repoName string,
) T {
	currentDB := rm.getDBFromContext(ctx)

	var repo Repository
	if _, ok := ctx.Value(txKey).(*gorm.DB); ok {
		repo = rm.txRepoFactoryRegistry.Get(repoName)(currentDB)
	} else {
		repo = rm.repoRegistry.Get(repoName)(currentDB)
	}

	return repo.(T)
}
func withDefaultContext(ctx []context.Context) context.Context {
	if len(ctx) > 0 && ctx[0] != nil {
		return ctx[0]
	}
	return context.Background()
}

func (rm *RepoManager) GameInfoRepo(ctx ...context.Context) GameInfoRepository {
	return getRepository[GameInfoRepository](withDefaultContext(ctx), rm, GameInfoRepoName)
}

func (rm *RepoManager) GameIntroductionRepo(ctx ...context.Context) GameIntroductionRepository {
	return getRepository[GameIntroductionRepository](withDefaultContext(ctx), rm, GameIntroductionRepoName)
}

func (rm *RepoManager) GameTheoryRepo(ctx ...context.Context) GameTheoryRepository {
	return getRepository[GameTheoryRepository](withDefaultContext(ctx), rm, GameTheoryRepoName)
}

func (rm *RepoManager) FirstTimeTeacherInfoRepo(ctx ...context.Context) FirstTimeTeacherInfoRepository {
	return getRepository[FirstTimeTeacherInfoRepository](withDefaultContext(ctx), rm, FirstTimeTeacherInfoRepoName)
}
func (rm *RepoManager) FirstTimePostportRepo(ctx ...context.Context) FirstTimePostportRepository {
	return getRepository[FirstTimePostportRepository](withDefaultContext(ctx), rm, FirstTimePostportRepoName)
}

func (rm *RepoManager) BeginTx(ctx context.Context) (context.Context, error) {
	// 检查是否已在事务中
	if _, ok := ctx.Value(txKey).(*gorm.DB); ok {
		return ctx, fmt.Errorf("transaction already in progress")
	}

	tx := rm.DB().Begin()
	if tx.Error != nil {
		return ctx, fmt.Errorf("failed to begin transaction: %w", tx.Error)
	}
	newCtx := context.WithValue(ctx, txKey, tx)
	log.Debug().Msg("db_loader.BeginTx: transaction started")
	return newCtx, nil
}
func (rm *RepoManager) TxDB(ctx context.Context) (*gorm.DB, error) {
	tx, ok := ctx.Value(txKey).(*gorm.DB)
	if !ok {
		return nil, fmt.Errorf("TxDB called outside of a transaction context")
	}
	return tx, nil
}
func (rm *RepoManager) CommitTx(ctx context.Context) error {
	tx, err := rm.getTxFromContext(ctx)
	if err != nil {
		return err
	}
	if err := tx.Commit().Error; err != nil {
		log.Error().Err(err).Msg("db_loader.CommitTx: failed to commit transaction")
		return fmt.Errorf("failed to commit transaction: %w", err)
	}
	log.Debug().Msg("db_loader.CommitTx: transaction committed successfully")
	return nil
}

func (rm *RepoManager) RollbackTx(ctx context.Context) error {
	tx, err := rm.getTxFromContext(ctx)
	if err != nil {
		log.Debug().Err(err).Msg("db_loader.RollbackTx: rollback called on a non-existent or finished transaction, likely already handled.")
		return nil
	}
	if err := tx.Rollback().Error; err != nil {
		log.Error().Err(err).Msg("db_loader.RollbackTx: failed to rollback transaction")
		return nil
	}
	log.Debug().Msg("db_loader.RollbackTx: transaction rolled back successfully")
	return nil
}

// getTxFromContext 是一个内部辅助函数，用于安全地从 context 中提取事务
func (rm *RepoManager) getTxFromContext(ctx context.Context) (*gorm.DB, error) {
	tx, ok := ctx.Value(txKey).(*gorm.DB)
	if !ok {
		return nil, fmt.Errorf("operation called outside of a transaction context")
	}
	if tx == nil {
		return nil, fmt.Errorf("transaction has already been committed or rolled back")
	}
	return tx, nil
}

func (rm *RepoManager) Close() error {
	if rm.db != nil {
		sqlDB, err := rm.db.DB()
		if err != nil {
			return fmt.Errorf("failed to get underlying sql.DB: %w", err)
		}
		return sqlDB.Close()
	}
	return nil
}
